<script setup>
import { 
    computed, 
    defineAsyncComponent, 
    onBeforeUnmount, 
    onMounted, 
    ref, 
    toRefs,
    watch, 
} from "vue";
import { useConfig } from "../useConfig";
import { 
    XMLNS, 
    createUid, 
    error, 
    getMissingDatasetAttributes, 
    objectIsEmpty, 
    treeShake,
    convertColorToHex,
    lightenHexColor,
    calculateNiceScale,
    applyDataLabel,
    dataLabel
} from "../lib";
import { throttle } from "../canvas-lib";
import { usePrinter } from "../usePrinter";
import { useLoading } from "../useLoading";
import { useSvgExport } from "../useSvgExport";
import { useNestedProp } from "../useNestedProp";
import { useResponsive } from "../useResponsive";
import { useThemeCheck } from "../useThemeCheck";
import { useUserOptionState } from "../useUserOptionState";
import { useChartAccessibility } from "../useChartAccessibility";
import themes from "../themes/vue_ui_bullet.json";
import Legend from "../atoms/Legend.vue"; // Must be ready in responsive mode
import Title from "../atoms/Title.vue"; // Must be ready in responsive mode
import img from "../img";
import BaseScanner from "../atoms/BaseScanner.vue";

const PackageVersion = defineAsyncComponent(() => import('../atoms/PackageVersion.vue'));
const PenAndPaper = defineAsyncComponent(() => import('../atoms/PenAndPaper.vue'));
const UserOptions = defineAsyncComponent(() => import('../atoms/UserOptions.vue'));

const { vue_ui_bullet: DEFAULT_CONFIG } = useConfig();
const { isThemeValid, warnInvalidTheme } = useThemeCheck();

const props = defineProps({
    config: {
        type: Object,
        default() {
            return {}
        }
    },
    dataset: {
        type: Object,
        default() {
            return {}
        }
    },
});

const bulletChart = ref(null);
const chartTitle = ref(null);
const titleStep = ref(0);
const chartLegend = ref(null);
const source = ref(null);
const step = ref(0);
const resizeObserver = ref(null);
const observedEl = ref(null);
const readyTeleport = ref(false);

const isDataset = computed({
    get: () => {
        return FINAL_DATASET.value.hasOwnProperty('value')
    },
    set: (bool) => {
        return bool
    }
})

const hasSegments = computed(() => {
    if(!FINAL_DATASET.value.segments) {
        console.warn(`VueUiBullet: dataset segments is empty. Provide segments with this datastructure:\n
segments: [
    {
        name: string;
        from: number;
        to: number;
        color?: string;
    },
    {...}
]
        `)
        isDataset.value = false;
        return false;
    }
    if(!Array.isArray(FINAL_DATASET.value.segments)) {
        console.warn(`VueUiBullet: dataset segments must be an array of objects with this datastructure:\n
segments: [
    {
        name: string;
        from: number;
        to: number;
        color?: string;
    },
    {...}
] 
        `);
        isDataset.value = false;
        return false;
    }
    if (!FINAL_DATASET.value.segments.length) {
        console.warn(`VueUiBullet: dataset segments is empty. Provide segments with this datastructure:\n
segments: [
    {
        name: string;
        from: number;
        to: number;
        color?: string;
    },
    {...}
]
        `);
        isDataset.value = false;
        return false
    };
    return true;
})

const debug = computed(() => !!FINAL_CONFIG.value.debug);

function prepareChart() {
    if(objectIsEmpty(FINAL_DATASET.value)) {
        error({
            componentName: 'VueUiBullet',
            type: 'dataset',
            debug: debug.value
        });
        manualLoading.value = true;
    } else {
        if (hasSegments.value) {
            FINAL_DATASET.value.segments.forEach((segment, i) => {
                getMissingDatasetAttributes({
                    datasetObject: segment,
                    requiredAttributes: ['name', 'from', 'to']
                }).forEach(attr => {
                    isDataset.value = false;
                    error({
                        componentName: 'VueUiBullet segment',
                        type: 'datasetSerieAttribute',
                        property: attr,
                        index: i,
                        debug: debug.value
                    })
                })
            })
        } else {
            isDataset.value = false;
            manualLoading.value = true;
        }
    }

    // v3
    if (!objectIsEmpty(FINAL_DATASET.value)) {
        manualLoading.value = FINAL_CONFIG.value.loading;
    }

    if (FINAL_CONFIG.value.responsive) {
        const handleResize = throttle(() => {
            const { width, height } = useResponsive({
                chart: bulletChart.value,
                title: FINAL_CONFIG.value.style.chart.title.text ? chartTitle.value : null,
                legend: FINAL_CONFIG.value.style.chart.legend.show ? chartLegend.value : null,
                source: source.value
            });

            requestAnimationFrame(() => {
                defaultSizes.value.width = width;
                defaultSizes.value.height = height - 12;
            })
        });

        if (resizeObserver.value) {
            if (observedEl.value) {
                resizeObserver.value.unobserve(observedEl.value);
            }
            resizeObserver.value.disconnect();
        }

        resizeObserver.value = new ResizeObserver(handleResize);
        observedEl.value = bulletChart.value.parentNode;
        resizeObserver.value.observe(observedEl.value);
    }

    if (FINAL_CONFIG.value.style.chart.animation.show && !loading.value) {
        useAnimation(FINAL_DATASET.value.value || 0);
    }
}

onMounted(() => {
    readyTeleport.value = true;
    prepareChart();
});

const uid = ref(createUid());

function prepareConfig() {
    const mergedConfig = useNestedProp({
        userConfig: props.config,
        defaultConfig: DEFAULT_CONFIG
    });

    const theme = mergedConfig.theme;
    if (!theme) return mergedConfig;

    if (!isThemeValid.value(mergedConfig)) {
        warnInvalidTheme(mergedConfig);
        return mergedConfig;
    }

    const fused = useNestedProp({
        userConfig: themes[theme] || props.config,
        defaultConfig: mergedConfig
    });

    const finalConfig = useNestedProp({
        userConfig: props.config,
        defaultConfig: fused
    });

    return finalConfig;
}

const FINAL_CONFIG = ref(prepareConfig());

const { loading, FINAL_DATASET, manualLoading } = useLoading({
    ...toRefs(props),
    FINAL_CONFIG,
    prepareConfig,
    skeletonDataset: {
        value: 100,
        target: 100,
        segments: [
            {
                name: '',
                from: 0,
                to: 33,
                color: '#AAAAAA'
            },
            {
                name: '',
                from: 33,
                to: 66,
                color: '#BABABA'
            },
            {
                name: '',
                from: 66,
                to: 100,
                color: '#CACACA'
            }
        ]
    },
    skeletonConfig: treeShake({
        defaultConfig: FINAL_CONFIG.value,
        userConfig: {
            userOptions: { show: false },
            style: {
                chart: {
                    backgroundColor: '#99999930',
                    segments: {
                        dataLabels: { show: false },
                        ticks: {
                            stroke: '#8A8A8A',
                        }
                    },
                    valueBar: {
                        label: { show: false }
                    }
                }
            }
        }
    })
})

const { userOptionsVisible, setUserOptionsVisibility, keepUserOptionState } = useUserOptionState({ config: FINAL_CONFIG.value });
const { svgRef } = useChartAccessibility({ config: FINAL_CONFIG.value.style.chart.title });

watch(() => props.config, (_newCfg) => {
    if (!loading.value) {
        FINAL_CONFIG.value = prepareConfig();
    }
    userOptionsVisible.value = !FINAL_CONFIG.value.userOptions.showOnChartHover;
    prepareChart();
    titleStep.value += 1;
}, { deep: true });

const defaultSizes = ref({
    width: FINAL_CONFIG.value.style.chart.width,
    height: FINAL_CONFIG.value.style.chart.height
});

const WIDTH = computed(() => defaultSizes.value.width);
const HEIGHT = computed(() => defaultSizes.value.height)

const svg = computed(() => {
    const height = HEIGHT.value;
    const width = WIDTH.value;
    const left = FINAL_CONFIG.value.style.chart.padding.left;
    const right = width - FINAL_CONFIG.value.style.chart.padding.right;
    const top = FINAL_CONFIG.value.style.chart.padding.top;
    const bottom = height - FINAL_CONFIG.value.style.chart.padding.bottom;
    return {
        height: Math.max(0.001, height),
        width: Math.max(0.001, width),
        left,
        right,
        top,
        bottom,
        chartWidth: Math.max(0.001, right - left),
        chartHeight: Math.max(0.001, bottom - top)
    }
})

const segmentColors = computed(() => {
    if(!hasSegments.value) return [];
    const arr = [];
    for(let i = 0; i < FINAL_DATASET.value.segments.length; i += 1) {
        arr.push(lightenHexColor(FINAL_CONFIG.value.style.chart.segments.baseColor, i / (FINAL_DATASET.value.segments.length)))
    }
    return arr;
})

const minMax = computed(() => {
    if (!hasSegments.value) return {min: 0, max: 1};
    return {
        min: Math.min(...FINAL_DATASET.value.segments.map(s => s.from)),
        max: Math.max(...FINAL_DATASET.value.segments.map(s => s.to)),
    }
})

const activeValue = ref(getActiveValue());

watch(() => FINAL_DATASET.value, (v) => {
    if (v.hasOwnProperty('value')) {
        manualLoading.value = false;
    }
    if (FINAL_CONFIG.value.style.chart.animation.show && !loading.value) {
        useAnimation(v.value || 0);
    } else {
        activeValue.value = v.value || 0
    }
}, { deep: true })

function getActiveValue() {
    if (FINAL_CONFIG.value.style.chart.animation.show && !loading.value) {
        return minMax.value.min;
    } else {
        return FINAL_DATASET.value.value || 0;
    }
}

const raf = ref(null);

function useAnimation(targetValue) {
    const chunk = Math.abs(targetValue - activeValue.value) / FINAL_CONFIG.value.style.chart.animation.animationFrames;
    function animate() {
        if(activeValue.value < targetValue) {
            activeValue.value = Math.min(activeValue.value + chunk, targetValue);
        } else if (activeValue.value > targetValue) {
            activeValue.value = Math.max(activeValue.value - chunk, targetValue);
        }
        if (activeValue.value !== targetValue) {
            raf.value = requestAnimationFrame(animate)
        }
    }
    animate();
}

onBeforeUnmount(() => {
    cancelAnimationFrame(raf.value);
})

const segments = computed(() => {
    if(!hasSegments.value) {
        return [];
    };
    const scale = calculateNiceScale(minMax.value.min, minMax.value.max, FINAL_CONFIG.value.style.chart.segments.ticks.divisions);
    const absMin = scale.min >= 0 ? 0 : Math.abs(scale.min);

    const target = {
        x: svg.value.left + (((FINAL_DATASET.value.target + absMin) / (scale.max + absMin)) * svg.value.chartWidth) - FINAL_CONFIG.value.style.chart.target.width / 2
    }
    const value = {
        width: ((activeValue.value + absMin) / (scale.max + absMin)) * svg.value.chartWidth
    }
    const ticks = scale.ticks.map(t => {
        return {
            value: t,
            y: svg.value.bottom + FINAL_CONFIG.value.style.chart.segments.dataLabels.fontSize + 3 + FINAL_CONFIG.value.style.chart.segments.dataLabels.offsetY,
            x: svg.value.left + (((t + absMin) / (scale.max + absMin)) * svg.value.chartWidth)
        }
    })
    return {
        scale,
        target,
        value,
        ticks,
        chunks: FINAL_DATASET.value.segments.map((segment, i) => {
            return {
                ...segment,
                color: segment.color ? convertColorToHex(segment.color) : segmentColors.value[i],
                x: svg.value.left + (svg.value.chartWidth * ((segment.from + absMin) / (scale.max + absMin))),
                y: svg.value.top,
                height: svg.value.chartHeight,
                width: svg.value.chartWidth * (Math.abs(segment.to - segment.from) / (scale.max + absMin)),
            }
        })
    }
})

const legendSet = computed(() => {
    if (!segments.value || !segments.value.chunks || !segments.value.chunks.length) {
        return []
    }
    return segments.value.chunks.map(segment => {
        const formattedFrom = applyDataLabel(
            FINAL_CONFIG.value.style.chart.segments.dataLabels.formatter,
            segment.from,
            dataLabel({
                p: FINAL_CONFIG.value.style.chart.segments.dataLabels.prefix,
                v: segment.from,
                s: FINAL_CONFIG.value.style.chart.segments.dataLabels.suffix,
                r: FINAL_CONFIG.value.style.chart.segments.dataLabels.rounding
            })
        )
        const formattedTo = applyDataLabel(
            FINAL_CONFIG.value.style.chart.segments.dataLabels.formatter,
            segment.to,
            dataLabel({
                p: FINAL_CONFIG.value.style.chart.segments.dataLabels.prefix,
                v: segment.to,
                s: FINAL_CONFIG.value.style.chart.segments.dataLabels.suffix,
                r: FINAL_CONFIG.value.style.chart.segments.dataLabels.rounding
            })
        );

        const value = `${formattedFrom} — ${formattedTo}`;

        return {
            ...segment,
            shape: 'square',
            value,
            display: `${segment.name}: ${value}`
        }
    });
})

const legendConfig = computed(() => {
    return {
        cy: 'bullet-div-legend',
        backgroundColor: 'transparent',
        color: FINAL_CONFIG.value.style.chart.legend.color,
        fontSize: FINAL_CONFIG.value.style.chart.legend.fontSize,
        paddingBottom: 6,
        fontWeight: FINAL_CONFIG.value.style.chart.legend.bold ? 'bold' : '',
    }
})

const { isPrinting, isImaging, generatePdf, generateImage } = usePrinter({
    elementId: `bullet_${uid.value}`,
    fileName: FINAL_CONFIG.value.style.chart.title.text || 'vue-ui-bullet',
    options: FINAL_CONFIG.value.userOptions.print
});

const hasOptionsNoTitle = computed(() => {
    return FINAL_CONFIG.value.userOptions.show && !FINAL_CONFIG.value.style.chart.title.text;
});

const isFullscreen = ref(false)
function toggleFullscreen(state) {
    isFullscreen.value = state;
    step.value += 1;
}

function getData() {
    return segments.value;
}

const isAnnotator = ref(false);
function toggleAnnotator() {
    isAnnotator.value = !isAnnotator.value;
}

async function getImage({ scale = 2} = {}) {
    if (!bulletChart.value) return
    const { width, height } = bulletChart.value.getBoundingClientRect();
    const aspectRatio = width / height;
    const { imageUri, base64 } = await img({ domElement: bulletChart.value, base64: true, img: true, scale})
    return { 
        imageUri, 
        base64, 
        title: FINAL_CONFIG.value.style.chart.title.text,
        width,
        height,
        aspectRatio
    }
}

const svgLegendItems = computed(() => {
    return legendSet.value.map(l => ({
        ...l,
        name: l.display
    }));
});

const svgBg = computed(() => FINAL_CONFIG.value.style.chart.backgroundColor);
const svgLegend = computed(() => FINAL_CONFIG.value.style.chart.legend);
const svgTitle = computed(() => FINAL_CONFIG.value.style.chart.title);

const { exportSvg, getSvg } = useSvgExport({
    svg: svgRef,
    title: svgTitle,
    legend: svgLegend,
    legendItems: svgLegendItems,
    backgroundColor: svgBg
});

function generateSvg({ isCb }) {
    if (isCb) {
        FINAL_CONFIG.value.userOptions.callbacks.svg(getSvg());
    } else {
        exportSvg();
    }
}

defineExpose({
    getData,
    getImage,
    generatePdf,
    generateImage,
    generateSvg,
    toggleAnnotator,
    toggleFullscreen
})

</script>

<template>
    <div
        ref="bulletChart"
        :class="`vue-data-ui-component vue-ui-bullet ${isFullscreen ? 'vue-data-ui-wrapper-fullscreen' : ''}`"
        :style="`font-family:${FINAL_CONFIG.style.fontFamily};width:100%;background:${FINAL_CONFIG.style.chart.backgroundColor}`"
        :id="`bullet_${uid}`"
        @mouseenter="() => setUserOptionsVisibility(true)" 
        @mouseleave="() => setUserOptionsVisibility(false)"
    >
        <PenAndPaper
            v-if="FINAL_CONFIG.userOptions.buttons.annotator"
            :svgRef="svgRef"
            :backgroundColor="FINAL_CONFIG.style.chart.backgroundColor"
            :color="FINAL_CONFIG.style.chart.color"
            :active="isAnnotator"
            @close="toggleAnnotator"
        >
            <template #annotator-action-close>
                <slot name="annotator-action-close"/>
            </template>
            <template #annotator-action-color="{ color }">
                <slot name="annotator-action-color" v-bind="{ color }"/>
            </template>
            <template #annotator-action-draw="{ mode }">
                <slot name="annotator-action-draw" v-bind="{ mode }"/>
            </template>
            <template #annotator-action-undo="{ disabled }">
                <slot name="annotator-action-undo" v-bind="{ disabled }"/>
            </template>
            <template #annotator-action-redo="{ disabled }">
                <slot name="annotator-action-redo" v-bind="{ disabled }"/>
            </template>
            <template #annotator-action-delete="{ disabled }">
                <slot name="annotator-action-delete" v-bind="{ disabled }"/>
            </template>
        </PenAndPaper>

        <div
            ref="noTitle"
            v-if="hasOptionsNoTitle" 
            class="vue-data-ui-no-title-space" 
            :style="`height:36px; width: 100%;background:transparent`"
        />

        <div ref="chartTitle" v-if="FINAL_CONFIG.style.chart.title.text" :style="`width:100%;background:transparent;`">
            <Title
                lineHeight="1.3rem"
                :key="`title_${titleStep}`"
                :config="{
                    title: {
                        cy: 'bullet-div-title',
                        ...FINAL_CONFIG.style.chart.title,
                    },
                    subtitle: {
                        cy: 'bullet-div-subtitle',
                        ...FINAL_CONFIG.style.chart.title.subtitle
                    }
                }"
            />
        </div>

        <div :id="`legend-top-${uid}`" />

        <UserOptions
            ref="details"
            v-if="FINAL_CONFIG.userOptions.show && isDataset && (keepUserOptionState ? true : userOptionsVisible)"
            :backgroundColor="FINAL_CONFIG.style.chart.backgroundColor"
            :color="FINAL_CONFIG.style.chart.color"
            :isPrinting="isPrinting"
            :isImaging="isImaging"
            :uid="uid"
            :hasTooltip="false"
            :hasPdf="FINAL_CONFIG.userOptions.buttons.pdf"
            :hasImg="FINAL_CONFIG.userOptions.buttons.img"
            :hasSvg="FINAL_CONFIG.userOptions.buttons.svg"
            :hasXls="false"
            :hasTable="false"
            :hasLabel="false"
            :hasFullscreen="FINAL_CONFIG.userOptions.buttons.fullscreen"
            :isFullscreen="isFullscreen"
            :chartElement="bulletChart"
            :position="FINAL_CONFIG.userOptions.position"
            :titles="{...FINAL_CONFIG.userOptions.buttonTitles }"
            :hasAnnotator="FINAL_CONFIG.userOptions.buttons.annotator"
            :isAnnotation="isAnnotator"
            :callbacks="FINAL_CONFIG.userOptions.callbacks"
            :printScale="FINAL_CONFIG.userOptions.print.scale"
            @toggleFullscreen="toggleFullscreen"
            @generatePdf="generatePdf"
            @generateImage="generateImage"
            @generateSvg="generateSvg"
            @toggleAnnotator="toggleAnnotator"
            :style="{
                visibility: keepUserOptionState ? userOptionsVisible ? 'visible' : 'hidden' : 'visible'
            }"
        >
            <template #menuIcon="{ isOpen, color }" v-if="$slots.menuIcon">
                <slot name="menuIcon" v-bind="{ isOpen, color }"/>
            </template>
            <template #optionPdf v-if="$slots.optionPdf">
                <slot name="optionPdf" />
            </template>
            <template #optionImg v-if="$slots.optionImg">
                <slot name="optionImg" />
            </template>
            <template #optionSvg v-if="$slots.optionSvg">
                <slot name="optionSvg" />
            </template>
            <template v-if="$slots.optionFullscreen" template #optionFullscreen="{ toggleFullscreen, isFullscreen }">
                <slot name="optionFullscreen" v-bind="{ toggleFullscreen, isFullscreen }"/>
            </template>
            <template v-if="$slots.optionAnnotator" #optionAnnotator="{ toggleAnnotator, isAnnotator }">
                <slot name="optionAnnotator" v-bind="{ toggleAnnotator, isAnnotator }" />
            </template>
        </UserOptions>

        <svg
            ref="svgRef"
            :xmlns="XMLNS"
            :class="{ 'vue-data-ui-fullscreen--on': isFullscreen, 'vue-data-ui-fulscreen--off': !isFullscreen, 'vue-ui-bullet-svg': true }"
            :viewBox="`0 0 ${svg.width} ${svg.height}`"
            :style="`width: 100%; overflow: visible; background:transparent;color:${FINAL_CONFIG.style.chart.color}`"
        >
            <PackageVersion />

            <!-- BACKGROUND SLOT -->
            <foreignObject 
                v-if="$slots['chart-background']"
                :x="0"
                :y="0"
                :width="svg.width"
                :height="svg.height"
                :style="{
                    pointerEvents: 'none'
                }"
            >
                <slot name="chart-background"/>
            </foreignObject>

            <g v-if="hasSegments">
                <!-- SEGMENTS -->
                <rect
                    data-cy="vue-ui-bullet-segment"
                    v-for="segment in segments.chunks"
                    :x="segment.x"
                    :y="segment.y"
                    :height="segment.height"
                    :width="segment.width"
                    :fill="segment.color"
                    :stroke-width="1"
                    :stroke="FINAL_CONFIG.style.chart.backgroundColor"
                    :style="{ transition: 'x 0.3s ease-in-out, width 0.3s ease-in-out' }"
                />
                <!-- TARGET BELOW-->
                <rect
                    data-cy="vue-ui-bullet-target-below"
                    v-if="!FINAL_CONFIG.style.chart.target.onTop && FINAL_CONFIG.style.chart.target.show"
                    :x="segments.target.x"
                    :y="svg.top + (svg.chartHeight - ((svg.chartHeight * FINAL_CONFIG.style.chart.target.heightRatio))) / 2"
                    :height="svg.chartHeight * FINAL_CONFIG.style.chart.target.heightRatio"
                    :width="FINAL_CONFIG.style.chart.target.width"
                    :rx="FINAL_CONFIG.style.chart.target.rounded ? FINAL_CONFIG.style.chart.target.width / 2 : 0"
                    :fill="FINAL_CONFIG.style.chart.target.color"
                    :stroke="FINAL_CONFIG.style.chart.target.stroke"
                    :stroke-width="FINAL_CONFIG.style.chart.target.strokeWidth"
                />
                <!-- VALUE BAR -->
                <rect
                    data-cy="vue-ui-bullet-value-bar"
                    :x="svg.left"
                    :y="svg.top + (svg.chartHeight - ((svg.chartHeight * FINAL_CONFIG.style.chart.valueBar.heightRatio))) / 2"
                    :height="svg.chartHeight * FINAL_CONFIG.style.chart.valueBar.heightRatio"
                    :width="segments.value.width"
                    :fill="FINAL_CONFIG.style.chart.valueBar.color"
                    :stroke="FINAL_CONFIG.style.chart.valueBar.stroke"
                    :stroke-width="FINAL_CONFIG.style.chart.valueBar.strokeWidth"
                />
                <!-- VALUE LABEL -->
                <text
                    data-cy="vue-ui-bullet-value-label"
                    v-if="FINAL_CONFIG.style.chart.valueBar.label.show"
                    :x="svg.left + segments.value.width"
                    :y="svg.top - 6 + FINAL_CONFIG.style.chart.valueBar.label.offsetY"
                    :font-size="FINAL_CONFIG.style.chart.valueBar.label.fontSize"
                    :font-weight="FINAL_CONFIG.style.chart.valueBar.label.bold ? 'bold' : 'normal'"
                    :fill="FINAL_CONFIG.style.chart.valueBar.label.color"
                    text-anchor="middle"
                >
                    {{ 
                        applyDataLabel(
                            FINAL_CONFIG.style.chart.segments.dataLabels.formatter,
                            activeValue,
                            dataLabel({
                                p: FINAL_CONFIG.style.chart.segments.dataLabels.prefix,
                                v: activeValue,
                                s: FINAL_CONFIG.style.chart.segments.dataLabels.suffix,
                                r: FINAL_CONFIG.style.chart.segments.dataLabels.rounding
                            })
                        )
                    }}
                </text>
                <!-- TARGET ON TOP-->
                <rect
                    data-cy="vue-ui-bullet-target-top"
                    v-if="FINAL_CONFIG.style.chart.target.onTop && FINAL_CONFIG.style.chart.target.show"
                    :x="segments.target.x"
                    :y="svg.top + (svg.chartHeight - ((svg.chartHeight * FINAL_CONFIG.style.chart.target.heightRatio))) / 2"
                    :height="svg.chartHeight * FINAL_CONFIG.style.chart.target.heightRatio"
                    :width="FINAL_CONFIG.style.chart.target.width"
                    :rx="FINAL_CONFIG.style.chart.target.rounded ? FINAL_CONFIG.style.chart.target.width / 2 : 0"
                    :fill="FINAL_CONFIG.style.chart.target.color"
                    :stroke="FINAL_CONFIG.style.chart.target.stroke"
                    :stroke-width="FINAL_CONFIG.style.chart.target.strokeWidth"
                    :style="{ transition: 'x 0.3s ease-in-out' }"
                />
                <!-- TICK LABELS -->
                <g v-if="FINAL_CONFIG.style.chart.segments.dataLabels.show">
                    <text
                        data-cy="vue-ui-bullet-tick-label"
                        v-for="tick in segments.ticks"
                        :x="tick.x"
                        :y="tick.y"
                        text-anchor="middle"
                        :fill="FINAL_CONFIG.style.chart.segments.dataLabels.color"
                        :font-size="FINAL_CONFIG.style.chart.segments.dataLabels.fontSize + 'px'"
                        :font-weight="FINAL_CONFIG.style.chart.segments.dataLabels.bold ? 'bold' : 'normal'"
                    >
                        {{ 
                            applyDataLabel(
                                FINAL_CONFIG.style.chart.segments.dataLabels.formatter,
                                tick.value,
                                dataLabel({
                                    p: FINAL_CONFIG.style.chart.segments.dataLabels.prefix,
                                    v: tick.value,
                                    s: FINAL_CONFIG.style.chart.segments.dataLabels.suffix,
                                    r: FINAL_CONFIG.style.chart.segments.dataLabels.rounding
                                })
                            )
                        }}
                    </text>
                </g>
                <!-- TICK MARKERS -->
                <g v-if="FINAL_CONFIG.style.chart.segments.dataLabels.show && FINAL_CONFIG.style.chart.segments.ticks.show">
                    <line
                        data-cy="vue-ui-bullet-tick-marker"
                        v-for="marker in segments.ticks"
                        :x1="marker.x"
                        :x2="marker.x"
                        :y1="svg.bottom"
                        :y2="svg.bottom + 3"
                        :stroke="FINAL_CONFIG.style.chart.segments.ticks.stroke"
                        :stroke-width="1"
                        stroke-linecap="round"
                    />
                </g>
            </g>
            <slot name="svg" :svg="svg"/>
        </svg>

        <div v-if="$slots.watermark" class="vue-data-ui-watermark">
            <slot name="watermark" v-bind="{ isPrinting: isPrinting || isImaging }"/>
        </div>

        <div :id="`legend-bottom-${uid}`" />

        <!-- LEGEND -->
        <Teleport v-if="readyTeleport" :to="FINAL_CONFIG.style.chart.legend.position === 'top' ? `#legend-top-${uid}` : `#legend-bottom-${uid}`">
            <div ref="chartLegend">
                <Legend 
                    v-if="FINAL_CONFIG.style.chart.legend.show"
                    :clickable="false"
                    :legendSet="legendSet"
                    :config="legendConfig"
                >
                    <template #item="{ legend }">
                        <div class="vue-ui-bullet-legend-item" dir="auto" v-if="!loading">
                            <span style="margin-right:2px">{{ legend.name }}:</span>
                            <span>{{ legend.value }}</span>
                        </div>
                    </template>
                </Legend>
                <slot name="legend" v-bind:legend="legendSet" />
            </div>
        </Teleport>

        <div v-if="$slots.source" ref="source" dir="auto">
            <slot name="source" />
        </div>

        <!-- v3 Skeleton loader -->
        <BaseScanner v-if="loading" />
    </div>
</template>

<style scoped>
@import "../vue-data-ui.css";
.vue-ui-bullet *{
    transition: unset;
}
.vue-ui-bullet {
    user-select: none;
    position: relative;
}
</style>